using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using System.Drawing.Imaging;
using System.Drawing;

namespace LabyrinthProject
{
    class AI : IDrawable
    {
        #region fields
        private Labyrinth labyrinth;
        private Matrix world;
        private Model model;

        private Random random;
        private Vector2 position;
        private Vector2 direction;
        private int maxDepth;
        private float height;

        private float speed;

        private Vector2 prevDirection;
        private Vector2 nextDirection;

        private float accumulatedDistance;
        private bool rotation;

        private Node previousNode;
        private Node currentNode;
        private Node nextNode;
        #endregion

        #region properties
        public Model Model
        {
            get { return model; }
            set { model = value; }
        }

        public float Speed
        {
            get { return speed; }
            set { speed = value; }
        }

        public Vector3 Position
        {
            get { return new Vector3(position.X, height, position.Y)*1000; }
            set { position = new Vector2(value.X, value.Z); }
        }

        public Vector3 Direction
        {
            get { return new Vector3(direction.X, 0, direction.Y); }
            set { direction = new Vector2(value.X, value.Z); }
        }
        #endregion

        #region intialization
        public AI(Model model, Labyrinth labyrinth, int position)
        {
            this.labyrinth = labyrinth;
            this.model = model;
            this.random = new Random();


            this.currentNode = labyrinth.ListOfNodeElements[position];
            this.position = currentNode.Position;
            this.nextNode = currentNode;
            this.maxDepth = 2;
            this.speed = 3f;
            this.labyrinth.ListOfNodeElements.Last().Value = -1;
            this.height = 0.4f;
        }
        #endregion

        #region methods
        public void Update(GameTime gameTime)
        {
            float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
            normalMode(elapsed);

            world = Matrix.Identity;
            world.Forward = Direction;
            world.Up = Vector3.Up;
            world.Right = Vector3.Cross(Direction, Vector3.Up);
            world.Translation = new Vector3(position.X, height, position.Y)*1000;

            Matrix rotacija = Matrix.CreateRotationX(MathHelper.ToRadians(180)) * Matrix.CreateRotationZ(MathHelper.Pi);
            Matrix scale = Matrix.CreateScale(4f);
            world =scale* rotacija * world;
        }

        public void Draw(Camera camera)
        {
            Matrix[] transforms = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(transforms);

            LabyrinthGame.GDevice.BlendState = BlendState.AlphaBlend;
            LabyrinthGame.GDevice.DepthStencilState = DepthStencilState.Default;

            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.World = transforms[mesh.ParentBone.Index] * world;
                    // Use the matrices provided by the chase camera
                    effect.View = camera.View;
                    effect.Projection = camera.Projection;
                    effect.Alpha = 0.5f;
                }
                mesh.Draw();
            }
        }

        private void normalMode(float elapsed)
        {
            if ((nextNode.Position - position).Length() < 0.5f)
            {
                //treba obrisat vrijednosti svim vrhovima nakon odredenog broja prolaska
                previousNode = currentNode;
                currentNode.Value++;
                currentNode = nextNode;
                nextNode = getNextNode(nextNode);
                currentNode.Value = nextNode.Value + 1;
                nextNode.Value = currentNode.Value;

                prevDirection = currentNode.Position - previousNode.Position;
                prevDirection.Normalize();

                nextDirection = nextNode.Position - currentNode.Position;
                nextDirection.Normalize();

                if (direction == Vector2.Zero)
                {
                    direction = nextDirection;
                    prevDirection = direction;
                }



                if (prevDirection != nextDirection)
                {
                    rotation = true;
                    accumulatedDistance = 0;
                }


            }

            if (rotation)
            {
                accumulatedDistance += speed * elapsed;
                float normalizedDistance = accumulatedDistance / ((float)System.Math.PI / 4f);

                if (prevDirection == -nextDirection)
                {
                    float angle = accumulatedDistance * 90;

                    direction = Vector2.Transform(prevDirection, Quaternion.CreateFromYawPitchRoll(0, 0, angle * (float)System.Math.PI / 180f));

                    if (angle > 180)
                    {
                        direction = nextDirection;
                        position = currentNode.Position + direction / 2f;
                        rotation = false;
                    }
                }
                else
                {

                    direction = (1 - normalizedDistance) * prevDirection + normalizedDistance * nextDirection;
                    direction.Normalize();



                    if (normalizedDistance > 1)
                    {
                        direction = nextDirection;
                        position = currentNode.Position + direction / 2f;
                        rotation = false;
                    }

                    position = position + direction * speed * elapsed;
                }

            }
            else
            {
                position = position + direction * speed * elapsed;
            }
        }

        private Node getNextNode(Node node)
        {
            List<Node> listOfSeenNodes = getListOfPosibleNodes(node);
            listOfSeenNodes.Add(node);


            List<double> listOfValues = new List<double>();

            foreach (Node tempNode in node.ListOfNeighborNodes())
            {
                listOfValues.Add(findValue(listOfSeenNodes, tempNode, maxDepth, (node.Position - tempNode.Position).Length()) + tempNode.Value);
            }


            var minValue = listOfValues.Min();
            return node.ListOfNeighborNodes()[listOfValues.IndexOf(minValue)];


        }

        private List<Node> getListOfPosibleNodes(Node node)
        {
            List<Node> listOfNodes = new List<Node>();

            foreach (Node tempNode in node.ListOfNeighborNodes())
            {
                listOfNodes.Add(tempNode);
            }

            return listOfNodes;
        }

        private double findValue(List<Node> listOfSeenNodes, Node node, int depth, float distance)
        {
            if (depth == 0)
            {
                return node.Value;
            }

            List<Node> listOfPosibleNodes = getListOfPosibleNodes(node);

            foreach (Node temp in listOfSeenNodes)
            {
                listOfPosibleNodes.Remove(temp);
            }

            if (listOfPosibleNodes.Count() == 0)
            {
                return node.Value;
            }


            listOfSeenNodes.AddRange(listOfPosibleNodes);

            List<double> values = new List<double>();
            foreach (Node newNode in listOfPosibleNodes)
            {
                values.Add(findValue(listOfSeenNodes, newNode, depth - 1, (node.Position - newNode.Position).Length()) + newNode.Value);

            }

            values.Add(node.Value);

            listOfSeenNodes.RemoveRange(listOfSeenNodes.Count() - listOfPosibleNodes.Count(), listOfPosibleNodes.Count());

            return values.Min();
        }
        
        #endregion
    }
}
